﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using BaoJianSoft.Common;
using BaoJianSoft.Common.Data;
using BaoJianSoft.Common.Validation;
using BaoJianSoft.ExtensionModel;
using BaoJianSoft.Platform.Initialization;
using BaoJianSoft.Platform.Properties;

namespace BaoJianSoft.Platform.Linq
{
	/// <summary>
	/// The database provider to CRUD organization and organization types by linq-to-sql.
	/// The provide only interacts with database without caching.
	/// The reason to create OrganizationDbProvider to separate database and cache for organization is to reduce the code complexity.
	/// </summary>
	internal class OrganizationDbProvider
	{
		private IAuthenticationContext authenticationContext;
		private IPlatformConfiguration platformConfiguration;
		private IHierarchyApi hierarchyApi;

		/// <summary>
		/// Construct IAuthenticationContext instance.
		/// </summary>
		/// <param name="authenticationContext"></param>
		/// <param name="platformConfiguration"></param>
		/// <param name="hierarchyApi"></param>
		public OrganizationDbProvider(IAuthenticationContext authenticationContext, IPlatformConfiguration platformConfiguration, IHierarchyApi hierarchyApi)
		{
			this.authenticationContext = authenticationContext;
			this.platformConfiguration = platformConfiguration;
			this.hierarchyApi = hierarchyApi;
		}

		#region Organization Type

		/// <summary>
		/// Save organization type object.
		/// </summary>
		/// <param name="organizationTypeObject"></param>
		/// <exception cref="ValidationException">etc organization type name does exist.</exception>
		public void Save(OrganizationTypeObject organizationTypeObject)
		{
			Kit.NotNull(organizationTypeObject, "organizationTypeObject");
			Kit.NotNull(organizationTypeObject.Name, "organizationTypeObject.Name");
			if (!this.platformConfiguration.Domains.Select(d => d.Value).Contains(organizationTypeObject.Domain))
				throw new ArgumentException(string.Format(Resources.InvalidOrganizationTypeDomain, organizationTypeObject.Domain), "organizationTypeObject.Domain");

			try
			{
				using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
				{
					OrganizationType organizationType = null;

					using (ValidationScope validationScope = new ValidationScope(true))
					{
						if (ctx.OrganizationTypes.Where(x => x.Name == organizationTypeObject.Name
							&& x.ApplicationId == this.authenticationContext.ApplicationId
							&& x.OrganizationTypeId != organizationTypeObject.OrganizationTypeId).Count() > 0)
						{
							validationScope.Error(Resources.ExistedOrganizationTypeName, organizationTypeObject.Name);
						}

						if (organizationTypeObject.OrganizationTypeId == Guid.Empty)
						{
							organizationType = new OrganizationType();
							ctx.OrganizationTypes.InsertOnSubmit(organizationType);
						}
						else
						{
							organizationType = ctx.OrganizationTypes.FirstOrDefault(orgType => orgType.OrganizationTypeId == organizationTypeObject.OrganizationTypeId);
							if (organizationType == null)
								validationScope.Error(Resources.InvalidOrganizationTypeID);

						}
					}

					organizationType.ApplicationId = this.authenticationContext.ApplicationId;
					organizationType.Domain = organizationTypeObject.Domain;
					organizationType.Name = organizationTypeObject.Name;
					organizationType.Description = organizationTypeObject.Description;
					organizationType.LastUpdatedDate = DateTime.Now;
					organizationType.Predefined = organizationTypeObject.Predefined;
					organizationType.DeleteStatus = organizationTypeObject.DeleteStatus;

					ctx.SubmitChanges();

					organizationTypeObject.OrganizationTypeId = organizationType.OrganizationTypeId;
					organizationTypeObject.LastUpdatedDate = organizationType.LastUpdatedDate;
				}
			}
			catch (ValidationException)
			{
				throw;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Get organization type business object by id.
		/// </summary>
		/// <param name="organizationTypeId">organization type id</param>
		/// <returns></returns>
		public OrganizationTypeObject GetOrganizationType(Guid organizationTypeId)
		{
			try
			{
				using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
				{
					return (from orgType in ctx.OrganizationTypes
							where orgType.OrganizationTypeId == organizationTypeId
							select new OrganizationTypeObject()
							{
								OrganizationTypeId = orgType.OrganizationTypeId,
								Name = orgType.Name,
								Domain = orgType.Domain,
								Description = orgType.Description,
								Predefined = orgType.Predefined,
								DeleteStatus = orgType.DeleteStatus,
								LastUpdatedDate = orgType.LastUpdatedDate
							}).FirstOrDefault();
				}
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Get organization type by name.
		/// </summary>
		/// <param name="organizationTypeName">organization type name</param>
		/// <returns></returns>
		public OrganizationTypeObject GetOrganizationType(string organizationTypeName)
		{
			try
			{
				using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
				{
					return (from orgType in ctx.OrganizationTypes
							where orgType.Name == organizationTypeName && orgType.ApplicationId == this.authenticationContext.ApplicationId
							select new OrganizationTypeObject()
							{
								OrganizationTypeId = orgType.OrganizationTypeId,
								Name = orgType.Name,
								Domain = orgType.Domain,
								Description = orgType.Description,
								Predefined = orgType.Predefined,
								DeleteStatus = orgType.DeleteStatus,
								LastUpdatedDate = orgType.LastUpdatedDate
							}).FirstOrDefault();
				}
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Find all organization types for current application.
		/// </summary>
		/// <returns></returns>
		public IEnumerable<OrganizationTypeObject> FindAllOrganizationTypes()
		{
			try
			{
				using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
				{
					var q = from orgType in ctx.OrganizationTypes
							where orgType.ApplicationId == this.authenticationContext.ApplicationId
							orderby orgType.Name
							select new OrganizationTypeObject()
							{
								OrganizationTypeId = orgType.OrganizationTypeId,
								Name = orgType.Name,
								Domain = orgType.Domain,
								Description = orgType.Description,
								LastUpdatedDate = orgType.LastUpdatedDate,
								Predefined = orgType.Predefined,
								DeleteStatus = orgType.DeleteStatus
							};

					return q.ToList();
				}
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		#endregion

		#region Organization

		/// <summary>
		/// Save organization business object. 
		/// If organizationObject.Id equals Guid.Empty, it means to save a new organization. 
		/// Otherwise it's updating an existed organization.
		/// </summary>
		/// <param name="organizationObject"></param>
		public void Save(OrganizationObject organizationObject)
		{
			Kit.NotNull(organizationObject, "organizationObject");
			Kit.NotNull(organizationObject.OrganizationCode, "organizationObject.OrganizationCode");
			Kit.NotNull(organizationObject.OrganizationName, "organizationObject.OrganizationName");

			if (organizationObject.OrganizationTypeId == Guid.Empty)
				throw new ArgumentException(Resources.InvalidOrganizationTypeID);

			if (organizationObject.Status == OrganizationStatus.None)
				throw new ArgumentException(Resources.OrganizationStatusNotSpecified, "organizationObject.Status");

			try
			{
				Organization organization = null;

				using (RwdTransactionScope transactionScope = new RwdTransactionScope())
				using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
				{
					using (ValidationScope validationScope = new ValidationScope(true))
					{
						// check duplicate organization name
						int existedOrganizationCount = (from org in ctx.Organizations
														where org.OrganizationName == organizationObject.OrganizationName
															 && org.ApplicationId == this.authenticationContext.ApplicationId
															 && org.OrganizationId != organizationObject.OrganizationId
														select org).Count();
						if (existedOrganizationCount > 0)
							validationScope.Error(Resources.ExistedOrganizationName, organizationObject.OrganizationName);

						// check duplicate organization code
						if (!string.IsNullOrEmpty(organizationObject.OrganizationCode))
						{
							existedOrganizationCount = (from org in ctx.Organizations
														where org.OrganizationCode == organizationObject.OrganizationCode
															 && org.ApplicationId == this.authenticationContext.ApplicationId
															 && org.OrganizationId != organizationObject.OrganizationId
														select org).Count();
							if (existedOrganizationCount > 0)
								validationScope.Error(Resources.ExistedOrganizationCode, organizationObject.OrganizationCode);
						}

						// validate organization type
						OrganizationTypeObject organizationTypeObject = this.GetOrganizationType(organizationObject.OrganizationTypeId);
						if (organizationTypeObject == null)
							validationScope.Error(Resources.InvalidOrganizationTypeID);

						// validate organization located area
						if (organizationObject.Hierarchies != null && organizationObject.Hierarchies.Count > 0)
						{
							foreach (string hierarchyType in organizationObject.Hierarchies.Keys)
							{
								HierarchyDataObject hierarchyDataObject = hierarchyApi.GetHierarchyData(organizationObject.Hierarchies[hierarchyType]);
								if (hierarchyDataObject == null || !string.Equals(hierarchyDataObject.HierarchyType, hierarchyType, StringComparison.OrdinalIgnoreCase))
									validationScope.Error(Resources.InvalidHierarchyAssociatedWithOrganization, hierarchyType, hierarchyDataObject.Id);
							}
						}

						// check circular reference of parent
						if (organizationObject.ParentOrganizationId.HasValue)
						{
							if (organizationObject.OrganizationId == organizationObject.ParentOrganizationId.Value)
								validationScope.Error(Resources.InvalidParentOrganizationID);

							if (organizationObject.OrganizationId != organizationObject.ParentOrganizationId.Value)
							{
								List<string> existedOrganizationNames = new List<string>();
								HashSet<Guid> existedOrganizationIds = new HashSet<Guid> { organizationObject.OrganizationId };
								OrganizationObject parentOrganizationObject = this.GetOrganizationInternal(organizationObject.ParentOrganizationId.Value);

								if (parentOrganizationObject == null)
									validationScope.Error(Resources.InvalidParentOrganizationID);
								else
								{
									this.VerifyStatusAgainstParentOrganization(parentOrganizationObject, organizationObject.Status, organizationObject.OrganizationName, validationScope);

									while (parentOrganizationObject != null)
									{
										existedOrganizationNames.Add(parentOrganizationObject.OrganizationName);
										if (existedOrganizationIds.Contains(parentOrganizationObject.OrganizationId))
										{
											validationScope.Error(Resources.ParentOrganizationCircularReference, FormatCircularOrganizationNames(existedOrganizationNames));
											break;
										}

										existedOrganizationIds.Add(parentOrganizationObject.OrganizationId);
										if (!parentOrganizationObject.ParentOrganizationId.HasValue) break;

										parentOrganizationObject = this.GetOrganizationInternal(parentOrganizationObject.ParentOrganizationId.Value);
									}
								}
							}
						}

						if (organizationObject.OrganizationId == Guid.Empty)
						{
							organization = ExtensionObjectFactory.Create<Organization>(organizationObject);
							organization.OrganizationCode = organizationObject.OrganizationCode;
							organization.CreatedDate = DateTime.Now;
							organization.CreatedBy = this.authenticationContext.User.UserId;

							ctx.Organizations.InsertOnSubmit(organization);
						}
						else
						{
							organization = ctx.Organizations.FirstOrDefault(org => org.OrganizationId == organizationObject.OrganizationId);
							if (organization == null)
								validationScope.Error(Resources.InvalidOrganizationID);

							if (organization.OrganizationCode != organizationObject.OrganizationCode)
								validationScope.Error(Resources.CodeCannotUpdate);

							// update status of all children only when updates status from Enabled to Disabled.
							if (organizationObject.Status == OrganizationStatus.Disabled && organization.Status == OrganizationStatus.Enabled)
								UpdateSubOrganizationsStatus(ctx, new[] { organization.OrganizationId }, organizationObject.Status);
						}
					}

					organization.ApplicationId = this.authenticationContext.ApplicationId;
					organization.OrganizationName = organizationObject.OrganizationName;
					organization.OrganizationTypeId = organizationObject.OrganizationTypeId;
					organization.ParentOrganizationId = organizationObject.ParentOrganizationId;
					organization.Status = organizationObject.Status;
					organization.Description = organizationObject.Description;
					organization.LastUpdatedDate = DateTime.Now;
					organization.LastUpdatedBy = this.authenticationContext.User.UserId;

					// remove original hierarchies and added new ones
					if (organizationObject.OrganizationId != Guid.Empty && organization.Hierarchies.Count > 0)
						ctx.OrganizationsInHierarchies.DeleteAllOnSubmit(organization.Hierarchies);

					if (organizationObject.Hierarchies != null && organizationObject.Hierarchies.Count > 0)
					{
						foreach (string hierarchyType in organizationObject.Hierarchies.Keys)
						{
							HierarchyDataObject hierarchyDataObject = hierarchyApi.GetHierarchyData(organizationObject.Hierarchies[hierarchyType]);
							if (hierarchyDataObject != null)
							{
								ctx.OrganizationsInHierarchies.InsertOnSubmit(new OrganizationsInHierarchy
									{
										ApplicationId = authenticationContext.ApplicationId,
										HierarchyType = hierarchyType,
										HierarchyDataId = organizationObject.Hierarchies[hierarchyType],
										Organization = organization,
									});
							}
						}
					}

					organization.ParseExtensionPropertiesFrom(organizationObject);

					ctx.SubmitChanges();
					transactionScope.Complete();

					organizationObject.OrganizationId = organization.OrganizationId;
					organizationObject.CreatedBy = organization.CreatedBy;
					organizationObject.CreatedDate = organization.CreatedDate;
					organizationObject.LastUpdatedBy = organization.LastUpdatedBy;
					organizationObject.LastUpdatedDate = organization.LastUpdatedDate;
				}
			}
			catch (ValidationException)
			{
				throw;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Find all organizations including the disabled and pending organizations.
		/// </summary>
		/// <returns></returns>
		public IEnumerable<OrganizationObject> FindAllOrganizations()
		{
			try
			{
				MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>();
				var q = ctx.Organizations.Where(org => org.ApplicationId == authenticationContext.ApplicationId);
				var tempVariables = q.Select(org =>
					new
					{
						Organization = org,
						OrganizationObject = new OrganizationObject()
						{
							OrganizationId = org.OrganizationId,
							OrganizationCode = org.OrganizationCode,
							OrganizationName = org.OrganizationName,
							OrganizationTypeId = org.OrganizationTypeId,
							Description = org.Description,
							Status = org.Status,
							CreatedBy = org.CreatedBy,
							CreatedDate = org.CreatedDate,
							LastUpdatedBy = org.LastUpdatedBy,
							LastUpdatedDate = org.LastUpdatedDate,
							ParentOrganizationId = org.ParentOrganizationId,
							HasChild = (from subOrganization in ctx.Organizations where subOrganization.ParentOrganizationId == org.OrganizationId select subOrganization).Count() > 0
						}
					}).ToList();

				Dictionary<Guid, Dictionary<string, Guid>> organizationHierarchies = (from hierarchy in ctx.OrganizationsInHierarchies
																					  where hierarchy.ApplicationId == authenticationContext.ApplicationId
																					  group hierarchy by hierarchy.OrganizationId into g
																					  select g).ToDictionary(g => g.Key, g => g.ToDictionary(kvp => kvp.HierarchyType, kvp => kvp.HierarchyDataId));

				List<OrganizationObject> returnValue = new List<OrganizationObject>();
				tempVariables.ForEach(variable =>
				{
					OrganizationObject organizationObject = variable.OrganizationObject;
					if (organizationHierarchies.ContainsKey(organizationObject.OrganizationId))
					{
						foreach (string hierarchyType in organizationHierarchies[organizationObject.OrganizationId].Keys)
						{
							Guid hierarchyDataId = organizationHierarchies[organizationObject.OrganizationId][hierarchyType];
							HierarchyDataObject hierarchyDataObject = hierarchyApi.GetHierarchyData(hierarchyDataId);
							if (hierarchyDataObject != null)
								organizationObject.Hierarchies[hierarchyDataObject.HierarchyType] = hierarchyDataObject.Id;
						}
					}

					organizationObject.ParseExtensionPropertiesFrom(variable.Organization);
					returnValue.Add(organizationObject);
				});

				return returnValue;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Find organization elements by enumerable organization ids.
		/// </summary>
		/// <param name="organizationIdList">enumerabe organization type ids, null or empty value indicates to query all organizations</param>
		/// <returns></returns>
		public IDictionary<Guid, OrganizationObject> BulkGetOrganizations(IEnumerable<Guid> organizationIdList)
		{
			try
			{
				if (organizationIdList.Count() == 0)
					return new Dictionary<Guid, OrganizationObject>();

				Dictionary<Guid, OrganizationObject> returnValue = new Dictionary<Guid, OrganizationObject>();

				Guid[] organizationIdArray = organizationIdList.ToArray();
				MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>();
				var q = ctx.Organizations.Where(org => organizationIdArray.Contains(org.OrganizationId) && org.ApplicationId == authenticationContext.ApplicationId);
				var tempVariables = q.Select(org =>
					new
					{
						Organization = org,
						OrganizationObject = new OrganizationObject()
						{
							OrganizationId = org.OrganizationId,
							OrganizationCode = org.OrganizationCode,
							OrganizationName = org.OrganizationName,
							OrganizationTypeId = org.OrganizationTypeId,
							Description = org.Description,
							Status = org.Status,
							CreatedBy = org.CreatedBy,
							CreatedDate = org.CreatedDate,
							LastUpdatedBy = org.LastUpdatedBy,
							LastUpdatedDate = org.LastUpdatedDate,
							ParentOrganizationId = org.ParentOrganizationId,
							HasChild = (from subOrganization in ctx.Organizations where subOrganization.ParentOrganizationId == org.OrganizationId select subOrganization).Count() > 0
						}
					}).ToList();

				Dictionary<Guid, Dictionary<string, Guid>> organizationHierarchies = (from hierarchy in ctx.OrganizationsInHierarchies
																					  where hierarchy.ApplicationId == authenticationContext.ApplicationId
																						&& organizationIdArray.Contains(hierarchy.OrganizationId)
																					  group hierarchy by hierarchy.OrganizationId into g
																					  select g).ToDictionary(g => g.Key, g => g.ToDictionary(kvp => kvp.HierarchyType, kvp => kvp.HierarchyDataId));

				tempVariables.ForEach(variable =>
				{
					OrganizationObject organizationObject = variable.OrganizationObject;
					if (organizationHierarchies.ContainsKey(organizationObject.OrganizationId))
					{
						foreach (string hierarchyType in organizationHierarchies[organizationObject.OrganizationId].Keys)
						{
							Guid hierarchyDataId = organizationHierarchies[organizationObject.OrganizationId][hierarchyType];
							HierarchyDataObject hierarchyDataObject = hierarchyApi.GetHierarchyData(hierarchyDataId);
							if (hierarchyDataObject != null)
								organizationObject.Hierarchies[hierarchyDataObject.HierarchyType] = hierarchyDataObject.Id;
						}
					}

					organizationObject.ParseExtensionPropertiesFrom(variable.Organization);
					returnValue[variable.OrganizationObject.OrganizationId] = organizationObject;
				});

				return returnValue;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Get organization instance by name.
		/// </summary>
		/// <param name="organizationName">organization name</param>
		/// <returns></returns>
		public OrganizationObject GetOrganizationByName(string organizationName)
		{
			try
			{
				using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
				{
					Guid organizationId = (from org in ctx.Organizations
										   where org.ApplicationId == this.authenticationContext.ApplicationId && org.OrganizationName == organizationName
										   select org.OrganizationId).FirstOrDefault();

					if (organizationId == Guid.Empty) return null;
					return GetOrganizationInternal(organizationId);
				}
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Get organization instance by code.
		/// </summary>
		/// <param name="organizationCode">organization code</param>
		/// <returns></returns>
		public OrganizationObject GetOrganizationByCode(string organizationCode)
		{
			try
			{
				using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
				{
					Guid organizationId = (from org in ctx.Organizations
										   where org.ApplicationId == this.authenticationContext.ApplicationId && org.OrganizationCode == organizationCode
										   select org.OrganizationId).FirstOrDefault();

					if (organizationId == Guid.Empty) return null;
					return GetOrganizationInternal(organizationId);
				}
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Find all enabled child organization ids includes not immediately.
		/// </summary>
		/// <param name="parentOrganizationId"></param>
		/// <returns></returns>
		public IEnumerable<Guid> FindChildOrganizationIds(Guid parentOrganizationId)
		{
			using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
			{
				List<Guid> allOrganizationIds = new List<Guid>();
				FindChildOrganizationIds(ctx, new[] { parentOrganizationId }, allOrganizationIds);
				return allOrganizationIds.Distinct();
			}
		}

		/// <summary>
		/// Find organization business objects by predicates.
		/// </summary>
		/// <param name="predicate">predicate in linq expression</param>
		/// <param name="dynamicPredicate">dynamic predicate in string format</param>
		/// <param name="orderby">dynamic orderby command</param>
		/// <param name="pageIndex">current paging index</param>
		/// <param name="pageSize">page size</param>
		/// <param name="recordCount">total hit records count</param>
		/// <param name="parameters">parameters to dynamic predicate</param>
		/// <returns>Returns enumerable organizations</returns>
		public IEnumerable<OrganizationObject> FindOrganizations(Expression<Func<Organization, bool>> predicate, string dynamicPredicate, string orderby, int pageIndex, int pageSize, out int recordCount, params object[] parameters)
		{
			try
			{
				using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
				{
					var q = from org in ctx.Organizations where org.ApplicationId == authenticationContext.ApplicationId select org;
					if (predicate != null)
						q = q.Where(predicate);

					if (!Kit.IsEmpty(dynamicPredicate))
						q = q.Where(dynamicPredicate, parameters);

					if (!Kit.IsEmpty(orderby))
						q = q.OrderBy(orderby);

					recordCount = q.Count();
					IEnumerable<Guid> organizationIds = q.Skip(pageIndex * pageSize).Take(pageSize).Select(org => org.OrganizationId);
					return this.BulkGetOrganizations(organizationIds).Values;
				}
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		private OrganizationObject GetOrganizationInternal(Guid organizationId)
		{
			IDictionary<Guid, OrganizationObject> organizationObjectDictionary = this.BulkGetOrganizations(new[] { organizationId });
			if (!organizationObjectDictionary.ContainsKey(organizationId)) return null;

			return organizationObjectDictionary[organizationId];
		}

		private void UpdateSubOrganizationsStatus(MembershipDataContext ctx, Guid[] parentOrganizationIds, OrganizationStatus status)
		{
			var q = from org in ctx.Organizations
					where org.ParentOrganizationId.HasValue
						&& parentOrganizationIds.Contains(org.ParentOrganizationId.Value)
						&& org.Status != status
					select org;

			if (q.Count() == 0) return;

			foreach (Organization organization in q.AsEnumerable())
				organization.Status = status;

			UpdateSubOrganizationsStatus(ctx, q.Select(org => org.OrganizationId).ToArray(), status);
		}

		private static void FindChildOrganizationIds(MembershipDataContext ctx, Guid[] parentOrganizationIds, List<Guid> allOrganizationIds)
		{
			var children = ctx.Organizations
				.Where(org => parentOrganizationIds.Contains(org.ParentOrganizationId.Value) && org.Status == OrganizationStatus.Enabled)
				.Select(org => org.OrganizationId);

			if (children.Count() > 0)
			{
				Guid[] parentOrganizationIdsToQuery = children.ToArray();
				allOrganizationIds.AddRange(parentOrganizationIdsToQuery);
				FindChildOrganizationIds(ctx, parentOrganizationIdsToQuery, allOrganizationIds);
			}
		}

		private static string FormatCircularOrganizationNames(IEnumerable<string> organizationNames)
		{
			StringBuilder output = new StringBuilder();
			foreach (string organizationName in organizationNames)
			{
				if (output.Length == 0) output.Append(organizationName);
				else output.AppendFormat(" -> {0}", organizationName);
			}

			return output.ToString();
		}

		/// <summary>
		/// Validation on if there specifies parent organization to new created one, the status of new organization must be lower priority than its parent.
		/// Organization status priority: Enabled &gt; Pending &gt; Disabled.
		/// </summary>
		/// <param name="parentOrganizationObject"></param>
		/// <param name="status"></param>
		/// <param name="organizationName"></param>
		/// <param name="validationScope"></param>
		private void VerifyStatusAgainstParentOrganization(OrganizationObject parentOrganizationObject, OrganizationStatus status, string organizationName, ValidationScope validationScope)
		{
			OrganizationStatus parentStatus = parentOrganizationObject.Status;
			if ((int)parentStatus < (int)status)
				validationScope.Error(Resources.OrganizationStatusCannotBeHigherThanParent, parentOrganizationObject.OrganizationName, parentStatus, organizationName, status);
		}

		#endregion
	}
}
